package com.flexoop.lib.components.arraycollection {

	import flash.events.Event;
	
	import mx.collections.ArrayCollection;

	[Bindable]
	public class PagedArrayCollection extends ArrayCollection {
		
		public static const SHOW_ALL:uint			= 0;
		public static const PAGING_COMPLETE:String	= "Paging Complete";
		
		/**
		 * @private
		 * 
		 * Allows referring to just one array throughout when dealing with a
		 * filtered vs. unfiltered array
		 */
		private var _pagedArray:Array = [];
		
		/**
		 * @private
		 * 
		 * Contains all records.  Used for splitting/paging
		 */
		// this allows for a read-only accessor, but also allows for binding
		[Bindable(event='originalArrayChanged')]
		private var __originalArray:Array = [];

		protected function get _originalArray():Array {
			return this.__originalArray;
		}
		protected function set _originalArray( value:Array ):void {
			this.__originalArray = value;
			this._pagedArray = this._originalArray;
			dispatchEvent( new Event( "originalArrayChanged" ) );
		}
		private var _preventSlice:Boolean = false;
		
		private var _perPage:uint = 0;
		private var _maxRecords:uint = PagedArrayCollection.SHOW_ALL;
		private var __totalPages:uint = 1;
		private var __currentPage:uint = 1;
		private var __totalRecords:uint = 0;
		private var __currentFirstRecord:uint = 0;
		private var __currentLastRecord:uint = 0;
		
		public function get perPage():uint {
			return this._perPage;
		}
		public function set perPage( value:uint ):void {
			// force perPage to be > 0 to prevent division by zero errors
			if ( value < 1 ) {
				value = 1;
			}
			_perPage = value;
			if ( _pagedArray ) {
				updateStats();
			} else {
				_totalPages = 1;
				_totalRecords = 0;
			}
			goTo();
		}
		
		public function get maxRecords():uint {
			return this._maxRecords;
		}
		public function set maxRecords( value:uint ):void {
			this._maxRecords = value;
		}
		
		// this allows for a read-only accessor, but also allows for binding
		[Bindable(event='totalPagesChanged')]
		public function get totalPages():uint {
			return _totalPages;
		}
		protected function get _totalPages():uint {
			return this.__totalPages;
		}
		protected function set _totalPages( value:uint ):void {
			this.__totalPages = value;
			dispatchEvent( new Event( "totalPagesChanged" ) );
		}
		
		// this allows for a read-only accessor, but also allows for binding
		[Bindable(event='currentPageChanged')]
		public function get currentPage():uint {
			return _currentPage;
		}
		protected function get _currentPage():uint {
			return __currentPage;
		}
		protected function set _currentPage( value:uint ):void {
			__currentPage = value;
			dispatchEvent( new Event( "currentPageChanged" ) );
		}
		
		// this allows for a read-only accessor, but also allows for binding
		[Bindable(event='totalRecordsChanged')]
		public function get totalRecords():uint {
			return _totalRecords;
		}
		protected function get _totalRecords():uint {
			return __totalRecords;
		}
		protected function set _totalRecords( value:uint ):void {
			__totalRecords = value;
			dispatchEvent( new Event( "totalRecordsChanged" ) );
		}
		
		// this allows for a read-only accessor, but also allows for binding
		[Bindable(event='currentFirstRecordChanged')]
		public function get currentFirstRecord():uint {
			return _currentFirstRecord;
		}
		protected function get _currentFirstRecord():uint {
			return this.__currentFirstRecord;
		}
		protected function set _currentFirstRecord( value:uint ):void {
			this.__currentFirstRecord = value;
			dispatchEvent( new Event( "currentFirstRecordChanged" ) );
		}
		
		// this allows for a read-only accessor, but also allows for binding
		[Bindable(event='currentLastRecordChanged')]
		public function get currentLastRecord():uint {
			return _currentLastRecord;
		}
		protected function get _currentLastRecord():uint {
			return this.__currentLastRecord;
		}
		protected function set _currentLastRecord( value:uint ):void {
			this.__currentLastRecord = value;
			dispatchEvent( new Event( "currentLastRecordChanged" ) );
		}
		
		/**
		 * @private
		 * 
		 * Builds all stats for collection
		 */
		private function updateStats():void {
			_totalRecords = _pagedArray.length;
			_totalPages = Math.ceil( _pagedArray.length / perPage );
		}
		
		/**
		 * @private
		 * 
		 * Allows limiting the array collection to the maximum records specified by the user
		 */
		private function limitArray():void {
			if ( _originalArray.length > maxRecords && maxRecords != PagedArrayCollection.SHOW_ALL ) {
				_originalArray = _originalArray.slice( 0, maxRecords );
			}
		}
		
		// if someone is trying to directly set the source, make sure the array has been sliced
		override public function set source( value:Array ):void {
			// if array has yet to be sliced
			if ( value.length > perPage ) {
				if ( localIndex ) {
					_pagedArray = localIndex;
				} else {
					// set originalArray to passed in array
					_originalArray = value;
					limitArray();
					_pagedArray = _originalArray;
				}
				if ( _preventSlice ) {
					super.source = _pagedArray;
				} else {
					super.source = _pagedArray.slice( 0, perPage );
				}
			} else {
				super.source = value;
			}
			updateStats();
		}
		
		// have to override refresh for paged AC
		override public function refresh():Boolean {
			/**
			 * reset source for filtering
			 */
			// set sorted/filtered array to null
			this.localIndex = null;
			// prevent slicing array
			this._preventSlice = true;
			// reset source
			this.source = this._originalArray;
			// localIndex is automagically updated when the source
			// is updated, so match source to new localIndex value
			this.source = ( localIndex && ( this.filterFunction || this.sort ) ) ? localIndex : _originalArray;
			// remove slice prevention
			this._preventSlice = false;
			_pagedArray = ( localIndex && ( this.filterFunction || this.sort ) ) ? localIndex : _originalArray;
			
			// update page information using filtered data
			_totalPages = ( localIndex && ( this.filterFunction || this.sort ) ) ? Math.ceil( localIndex.length / perPage ) : _originalArray ? Math.ceil( _originalArray.length / perPage ) : 1;
			_totalRecords = ( localIndex && ( this.filterFunction || this.sort ) ) ? localIndex.length : _originalArray ? _originalArray.length : 0;
			
			goTo();
			
			return true;
		}
		
		override public function addItem( item:Object ):void {
			this._originalArray.push( item );
			_pagedArray = _originalArray;
			updateStats();
		}
		
		override public function addItemAt( item:Object, index:int ):void {
			this._originalArray.splice( index, 0, item );
			_pagedArray = _originalArray;
			updateStats();
		}
		
		override public function removeItemAt( index:int ):Object {
			return this._originalArray.splice( index, 1 );
		}
		
		public function PagedArrayCollection( arrSource:Array, totalPerPage:uint=25, totalMaxRecords:uint=200 ) {
			super();
			_originalArray = arrSource;
			maxRecords = totalMaxRecords;
			limitArray();
			perPage = totalPerPage;
		}
		
		public function next():void {
			if ( currentPage < totalPages ) {
				goTo( currentPage + 1 );
			}
		}
		
		public function previous():void {
			if ( currentPage > 1 ) {
				goTo( currentPage - 1 );
			}
		}
		
		public function first():void {
			goTo();
		}
		
		public function last():void {
			goTo( totalPages );
		}
		
		public function goTo( page:uint=1 ):void {
			if ( page > totalPages ) {
				page = totalPages;
			} else if ( page < 1 ) {
				page = 1;
			}
			_currentPage = page;
			var _startIndex:uint = ( page - 1 ) * perPage;
			if ( _pagedArray ) {
				this.source = _pagedArray.slice( _startIndex, _startIndex + perPage );
			}
			_currentFirstRecord = _startIndex + 1;
			if ( _currentFirstRecord > totalRecords ) {
				_currentFirstRecord = totalRecords;
			}
			_currentLastRecord = _startIndex + perPage;
			if ( _currentLastRecord > totalRecords ) {
				_currentLastRecord = totalRecords;
			}
			this.dispatchEvent( new Event( PagedArrayCollection.PAGING_COMPLETE ) );
		}
		
		public function sortOn( fieldName:Object, options:Object=Array.CASEINSENSITIVE ):void {
			if ( _pagedArray ) {
				_pagedArray.sortOn( fieldName, options );
				// set sorted array to PagedArrayCollection
				this.source = _pagedArray;
				// reset everything in view
				goTo();
			}
		}
		
	}
}